package com.nulldev.util.web.twitch;

import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.util.regex.Pattern;

import com.nulldev.util.java.DataSet;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.lang.json.Json;
import com.nulldev.util.lang.json.JsonObject;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.string.StringsUtil;
import com.nulldev.util.threading.v3.ThreadDispatcher;
import com.nulldev.util.threading.v3_extensions.Task;
import com.nulldev.util.uri.URIUtil;
import com.nulldev.util.uri.URIUtil2;
import com.nulldev.util.uri.Soup_imported.Jsoup;
import com.nulldev.util.uri.Soup_imported.nodes.Document;
import com.nulldev.util.uri.Soup_imported.nodes.Element;
import com.nulldev.util.uri.Soup_imported.select.Elements;
import com.nulldev.util.uri.URIUtil2.IO.Entry;

public class TwitchTV {
	private String clientKey = null;
	
	private final Task<String> accquireClientKeyTask = new Task<String>() {

		@Override
		public String run() {
			String data0;
			try {
				data0 = URIUtil2.PortedLegacyDownloader.downloadPage(
						URIUtil.safeCreate("https://twitch.tv/"), true).getB();
			} catch (IOException e1) {
				throw new RuntimeException(e1);
			}
			final Document doc = Jsoup.parse(data0);
			final Elements scripts = doc.getElementsByTag("script");
			String coreScript = null;
			for (final Element e : scripts) {
				if (e.hasAttr("src")) {
					final String attr = e.attr("src");
					if (attr.startsWith("https://static.twitchcdn.net/assets/core-") && attr.endsWith(".js")) {
						coreScript = attr;
						break;
					}
				}
			}
			JVMUtil.async_println("apiURL: " + coreScript);
			if (coreScript == null) {
				return null;
			}
			String data;
			try {
				data = URIUtil2.PortedLegacyDownloader.downloadPage(
						URIUtil.safeCreate(coreScript), true).getB();
			} catch (IOException e1) {
				throw new RuntimeException(e1);
			}
			final String apiKey = StringsUtil.substringBetweenO(data, "{clientID:\"", "\",");
			return apiKey;
		}
		
	};
	
	public TwitchTV() {
		ThreadDispatcher.instance().addTask(this.accquireClientKeyTask);
	}
	
	public TwitchTV(final String key) {
		this.clientKey = key;
	}
	
	public DataSet<String, String> generateToken(final String channel) throws Exception {
		if (this.clientKey == null && !this.accquireClientKeyTask.completed()) {
			this.clientKey = this.accquireClientKeyTask.waitTillCompletion();
		}
		final String oAuthToken = this.clientKey;
		final String URL = "https://api.twitch.tv/api/channels/" + channel + 
				"/access_token?adblock=false&need_https=true&oauth_token=" + oAuthToken + "&platform=web&player_type=site";
		final DataSet<String, Integer> response = URIUtil2.IO.send(URIUtil.safeCreate(URL),
				new Entry("client-id", this.clientKey));
		if (response.getB() == 200) {
			final JsonObject obj = Json.parse(response.getA()).asObject();
			final String sig = obj.getString("sig");
			final String token = obj.getString("token");
			return new DataSet<String, String>(token, sig);
		}else {
			throw new IOException("Got response: " + response.getB());
		}
	}
	
	public String getStreamM3U8_URL(final String channel, final DataSet<String, String> tokenData) throws Exception {
		final String URL = 
				"http://usher.twitch.tv/api/channel/hls/" + channel + ".m3u8?" +
					"player=twitchweb&token=" + tokenData.getA() + "&sig=" + tokenData.getB() + "&allow_audio_only=true&" +
						"allow_source=true&type=any&p=" + MathUtil.randInt(10000, 99999);
		return URL.replaceAll(Pattern.quote("\""), "%22")
				.replaceAll(Pattern.quote(" "), "%20");
	}
	
	public String getStreamM3U8_URL_LowLatency(final String channel, final DataSet<String, String> tokenData) throws Exception {
		final String URL = 
				//"http://usher.ttvnw.net/api/channel/hls/" + channel + ".m3u8?"
						"http://usher.twitch.tv/api/channel/hls/" + channel + ".m3u8?" 
						+ "allow_source=true&baking_bread=true&baking_brownies=true&baking_brownies_timeout=1050&"
						+ "fast_bread=true&player_backend=mediaplayer&playlist_include_framerate=true&"
						+ "reassignments_supported=false&rtqos=control"
						+ "&type=any&p=" + MathUtil.randInt(10000, 99999)
						+ "&sig=" + tokenData.getB()
						+ "&token=" + tokenData.getA()
						+ "&cdm=wv";
		return URL.replaceAll(Pattern.quote("\""), "%22")
				.replaceAll(Pattern.quote(" "), "%20");
	}
	
	public String getKey() {
		return this.clientKey;
	}
	
	public boolean isStreaming(final String channel) {
		if (channel == null || channel.contains(" ") || channel.isEmpty()) {
			return false;
		}
		if (this.clientKey == null && !this.accquireClientKeyTask.completed()) {
			this.clientKey = this.accquireClientKeyTask.waitTillCompletion();
		}
		try {
			final String URL = "https://api.twitch.tv/kraken/streams/" + channel + "?client_id=" + this.clientKey;
			final DataSet<Integer, String> resp = URIUtil2.PortedLegacyDownloader.downloadPage(URIUtil.safeCreate(URL), true);
			final JsonObject obj = Json.parse(resp.getB()).asObject();
			return obj.contains("stream") && obj.get("stream") != null && !obj.get("stream").toString().equals("null");
		}catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}
	
	public static class StreamerMeta {
		private final boolean isStreaming;
		private final String currentGame;
		private final long viewers;
		private final String displayableString, minorString;
		private final String channelName, channel;
		
		protected StreamerMeta(final JsonObject jsonData, final String channelName) {
			this.channel = channelName;
			this.isStreaming = jsonData.get("stream") != null && !jsonData.get("stream").toString().equals("null");
			if (this.isStreaming) {
				final JsonObject stream = jsonData.get("stream").asObject();
				this.currentGame = stream.getString("game");
				this.viewers = stream.getLong("viewers", -1);
				if (stream.contains("channel")) {
					final JsonObject channel = stream.get("channel").asObject();
					this.displayableString = channel.getString("status");
					this.channelName = channel.getString("display_name");
					this.minorString = this.channelName + " is playing " + this.currentGame + ".";
				}else {
					this.displayableString = null;
					this.channelName = channelName;
					this.minorString = null;
				}
			}else {
				this.currentGame = null;
				this.viewers = -1L;
				this.displayableString = null;
				this.channelName = channelName;
				this.minorString = null;
			}
		}
		
		public boolean isStreaming() {
			return this.isStreaming;
		}
		
		public long getViewers() {
			return this.viewers;
		}
		
		public String getGame() {
			return this.currentGame;
		}
		
		public boolean hasGame() {
			return this.currentGame != null;
		}
		
		public String getDisplayableString() {
			return this.displayableString;
		}
		
		public boolean hasDisplayableString() {
			return this.displayableString != null;
		}
		
		public JsonObject get() {
			final JsonObject obj = new JsonObject();
			obj.add("streaming", this.isStreaming);
			obj.add("currentGame", this.currentGame);
			obj.add("viewers", this.viewers);
			obj.add("displayString", this.displayableString);
			obj.add("minorString", this.minorString);
			obj.add("channelName", this.channelName);
			obj.add("chatURL", this.getChatURL());
			obj.add("darkChatURL", this.getDarkChatURL());
			return obj;
		}
		
		public String getChannelName() {
			return this.channelName;
		}
		
		public String getMinorString() {
			return this.minorString;
		}
		
		public String getChatURL() {
			return "https://www.twitch.tv/embed/" + this.channel + "/chat";
		}
		
		public String getDarkChatURL() {
			return "https://www.twitch.tv/embed/" + this.channel + "/chat?darkpopout";
		}
	}
	
	public StreamerMeta accquire(final String channel) {
		if (channel == null || channel.contains(" ") || channel.isEmpty()) {
			return null;
		}
		if (this.clientKey == null && !this.accquireClientKeyTask.completed()) {
			this.clientKey = this.accquireClientKeyTask.waitTillCompletion();
		}
		try {
			final String URL = "https://api.twitch.tv/kraken/streams/" + channel + "?client_id=" + this.clientKey;
			final DataSet<Integer, String> resp = URIUtil2.PortedLegacyDownloader.downloadPage(URIUtil.safeCreate(URL), true);
			final JsonObject obj = Json.parse(resp.getB()).asObject();
			return new StreamerMeta(obj, channel);
		}catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	/*
	public static String getStreamM3U8(final String channel, final String apiKey,
			final String token, final String sig) throws Exception {
		final String URL = 
				"https://usher.ttvnw.net/api/channel/hls/" + channel + ".m3u8?"
						+ "allow_source=true&baking_bread=true&baking_brownies=true&baking_brownies_timeout=1050&"
						//+ "fast_bread=true&player_backend=mediaplayer&playlist_include_framerate=true&"
						//+ "reassignments_supported=false&rtqos=control"
						+ "&type=any&p=" + MathUtil.randInt(10000, 99999)
						+ "&sig=" + sig
						+ "&token=" + token
						+ "&cdm=wv";
		JVMUtil.async_println("URL: \n" + URL);
		//final String result = new String(
		//		URIUtil.downloadPageFB(URIUtil.safeCreate(URL), 
		//				true, Proxy.NO_PROXY, URIUtil2.URI_UTIL2_USER_AGENT));
		return new String(
						URIUtil.downloadPageFB(URIUtil.safeCreate(URL), 
								true, Proxy.NO_PROXY, URIUtil2.URI_UTIL2_USER_AGENT));
	}

	/
	 * 
	 * @param channel
	 * @param apiKey
	 * @returns DataSet[token, sig]
	 * @throws Exception
	 *
	public static DataSet<String, String> generateToken(final String channel, final String apiKey) throws Exception {
		if (apiKey == null || apiKey.length() == 0) {
			return null;
		}
		//, final String oAuthToken
		final String oAuthToken = apiKey;
		final String URL = "https://api.twitch.tv/api/channels/" + channel + 
				"/access_token?adblock=false&need_https=true&oauth_token=" + oAuthToken + "&platform=web&player_type=site";
		final DataSet<String, Integer> response = URIUtil2.IO.send(URIUtil.safeCreate(URL),
				new Entry("client-id", apiKey));
		if (response.getB() == 200) {
			final JsonObject obj = Json.parse(response.getA()).asObject();
			final String sig = obj.getString("sig");
			final String token = obj.getString("token");
			return new DataSet<String, String>(token, sig);
		}
		return null;
	}
	
	public static String getAPIKey() throws Exception {
		final String data0 = URIUtil.downloadPage(URIUtil.safeCreate("https://twitch.tv/"));
		final Document doc = Jsoup.parse(data0);
		final Elements scripts = doc.getElementsByTag("script");
		String coreScript = null;
		for (final Element e : scripts) {
			if (e.hasAttr("src")) {
				final String attr = e.attr("src");
				if (attr.startsWith("https://static.twitchcdn.net/assets/core-") && attr.endsWith(".js")) {
					coreScript = attr;
					break;
				}
			}
		}
		JVMUtil.async_println("apiURL: " + coreScript);
		if (coreScript == null) {
			return null;
		}
		final String data = URIUtil.downloadPage(URIUtil.safeCreate(coreScript));
		final String apiKey = StringsUtil.substringBetweenO(data, "{clientID:\"", "\",");
		return apiKey;
	}
	
	public static void main(String[] args) {
		try {
			final String channel = "forsen"; // *inaudible screeching*
			final String apiKey = getAPIKey();
			System.out.println("Twitch.tv API Key: " + apiKey);
			final DataSet<String, String> s = generateToken(channel, apiKey);
			System.out.println("token: " + s.getA());
			System.out.println("sig: " + s.getB());
			final byte[] data = getStreamM3U8(channel, apiKey, s.getA(), s.getB()).getBytes();
			try {
				final ByteArrayInputStream bis = new ByteArrayInputStream(data);
				final PlaylistParser plp = new PlaylistParser(PlaylistType.M3U8);
				final Playlist pl = plp.parse(new InputStreamReader(bis));
				System.out.println(pl);
				System.out.println("PL.mediaSequence: " + pl.getMediaSequenceNumber());
				System.out.println("PL.duration: " + pl.getTargetDuration());
				ArrayUtils.printLln(pl.getElements());
			}catch (Exception e) {
				System.out.println("Failed to get data.");
				System.out.println(new String(data));
			}
		}catch (Exception e) { 
			e.printStackTrace();
		}
	}

	public static String getStreamM3U8URL(final String channel, final String apiKey,
			final String token, final String sig) {
		return "https://usher.ttvnw.net/api/channel/hls/" + channel + ".m3u8?"
				+ "allow_source=true&baking_bread=true&baking_brownies=true&baking_brownies_timeout=1050&"
				//+ "fast_bread=true&p=4759350&player_backend=mediaplayer&playlist_include_framerate=true&"
				//+ "reassignments_supported=false&rtqos=control"
				+ "&type=any&p=" + MathUtil.randInt(10000, 99999)
				+ "&sig=" + sig
				+ "&token=" + token
				+ "&cdm=wv";
	}*/
	
	public static void main(String[] args) throws Exception {
		URIUtil.disableCertificateValidation();
		
		final String channel = "gamesdonequick";
		
		final TwitchTV ttv = new TwitchTV();
		JVMUtil.async_println("Streaming: " + ttv.isStreaming("funke"));
		final DataSet<String, String> initalData = ttv.generateToken(channel);
		initalData.println();
		final String url = ttv.getStreamM3U8_URL_LowLatency(channel, initalData);
		JVMUtil.async_println("0 URL: " + url);
		final URL url0 = new URL(url);
		JVMUtil.async_println("1 URL: " + url0);
		final byte[] data = URIUtil.downloadPageFB(url0,
				true, Proxy.NO_PROXY, URIUtil2.URI_UTIL2_USER_AGENT, true, false, 
				new Entry("Accept", "application/x-mpegURL, application/vnd.apple.mpegurl, application/json, text/plain"),
				new Entry("Origin", "https://www.twitch.tv"),
				new Entry("Referer", "https://www.twitch.tv/" + channel),
				new Entry("Host", "usher.ttvnw.net"));
	}
}
